﻿// <copyright file="AppNotificationSender.cs" company="Robert Venables">
// Copyright (c) 2009 Robert Venables.
// </copyright>
// <author>Robert Venables</author>
// <email>rob@rvenables.com</email>

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

namespace VNotify
{
    /// <summary>
    /// Sends AppNotification objects through the "AppNotifications" service.
    /// </summary>
    internal class AppNotificationSender : IAppNotificationSender
    {
        /// <summary>
        /// The AppNotification which is in the process of being sent.
        /// </summary>
        private AppNotification currentNotification = new AppNotification();

        /// <summary>
        /// The object which is locked when sending, preventing multiple
        /// threads from sending through the same AppNotificationSender
        /// instance.
        /// </summary>
        private object notificationSendLock = new object();

        /// <summary>
        /// Gets the API Key used to identify the recipient.
        /// </summary>
        public string ApiKey { get; private set; }

        /// <summary>
        /// Initializes the AppNotification sender with the required API key.
        /// </summary>
        /// <param name="apiKey">The API key used to identify the recipient.</param>
        public void Initialize(string apiKey)
        {
            if (string.IsNullOrEmpty(apiKey) || apiKey.Length < 4)
                throw new ArgumentException("API Key Invalid");

            this.ApiKey = apiKey;
        }

        /// <summary>
        /// Sends Source notification synchronously.
        /// </summary>
        /// <param name="source">The source AppNotification to be sent.</param>
        public void SendNotification(AppNotification source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            this.SendNotificationWork(source);
        }

        /// <summary>
        /// Sends the specified AppNotification using the API key
        /// stipulated during initialization.
        /// </summary>
        /// <param name="originalSource">The AppNotification to send.</param>
        private void SendNotificationWork(AppNotification originalSource)
        {
            if (originalSource == null)
                throw new ArgumentNullException("originalSource");

            System.Diagnostics.Debug.Assert(
                string.IsNullOrEmpty(this.ApiKey) == false && this.ApiKey.Length > 4,
                "Invalid API Key!");

            lock (this.notificationSendLock)
            {
                // Deep copy to isolate from potential changes in
                // another thread.
                this.currentNotification = originalSource.DeepCopy();
 
                // Convert the app notification into a post data format
                // that the target server understands
                AppNotificationPostDataBuilder postDataBuilder 
                    = new AppNotificationPostDataBuilder(this.currentNotification, this.ApiKey);

                // Setup the target post URI
                UriBuilder targetURIBuilder = new UriBuilder()
                {
                    Scheme = Uri.UriSchemeHttps,
                    Host = "www.appnotifications.com",
                    Path = "/account/notifications.xml"
                };

                // Build the main web request
                HttpWebRequest request
                    = HttpWebRequest.Create(targetURIBuilder.Uri) as HttpWebRequest;
                    
                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded";
                request.UserAgent = "AppNotifications RNotify";

                // Get the encoded post data in bytes
                byte[] postBytes = Encoding.UTF8.GetBytes(
                    postDataBuilder.EncodedPostValues);

                request.ContentLength = postBytes.Length;

                // Write post data byte array (post notification data)
                using (Stream postStream = request.GetRequestStream())
                {
                    postStream.Write(postBytes, 0, postBytes.Length);
                }

                using (HttpWebResponse response
                    = request.GetResponse() as HttpWebResponse)
                {
                    // Check for negative response from server
                    if (response == null || !(
                        response.StatusCode == HttpStatusCode.OK ||
                        response.StatusCode == HttpStatusCode.Accepted))
                    {
                        throw new System.Net.WebException(
                            string.Format(
                                CultureInfo.CurrentCulture,
                                "Negative response from server: {0}",
                                response.StatusCode.ToString()));
                    }
                }
            }
        }
    }
}